package com.ynet.middleground.mobile.approve.service.impl;

import java.time.LocalDateTime;
import java.util.*;
import java.util.stream.Collectors;

import org.apache.commons.lang3.StringUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ynet.core.common.ServiceResult;
import com.ynet.core.exception.BusinessException;
import com.ynet.core.log.IfpLogger;
import com.ynet.core.util.IDUtil;
import com.ynet.middleground.approve.bean.BusinessSignInfoData;
import com.ynet.middleground.approve.bean.CommentFile;
import com.ynet.middleground.approve.bean.FilesToBeSignedBean;
import com.ynet.middleground.approve.bean.QueryTaskDetailReq;
import com.ynet.middleground.approve.constant.OperatingTypeEnum;
import com.ynet.middleground.approve.constant.WorkflowConstants;
import com.ynet.middleground.approve.dto.TaskDTO;
import com.ynet.middleground.approve.service.ProcessTaskService;
import com.ynet.middleground.contract.bean.SignPos;
import com.ynet.middleground.contract.bean.SignResultBean;
import com.ynet.middleground.contract.bean.SigningParamBean;
import com.ynet.middleground.contract.dto.MultiSignedResultDTO;
import com.ynet.middleground.contract.dto.SignedResultDTO;
import com.ynet.middleground.mobile.approve.constant.MsgTypeEnum;
import com.ynet.middleground.mobile.approve.constant.SystemConstant;
import com.ynet.middleground.mobile.approve.dao.SignCodeMapper;
import com.ynet.middleground.mobile.approve.dao.TaskOperateStageMapper;
import com.ynet.middleground.mobile.approve.entity.SignCode;
import com.ynet.middleground.mobile.approve.entity.TaskOperateStage;
import com.ynet.middleground.mobile.approve.request.CompleteTaskReq;
import com.ynet.middleground.mobile.approve.request.UploadFileBean;
import com.ynet.middleground.mobile.approve.response.BusinessFormDataForFile;
import com.ynet.middleground.mobile.approve.service.*;
import com.ynet.middleground.mobile.approve.utils.TransactionUtils;
import com.ynet.middleground.user.dto.UserInfoDto;

/**
 * <p>
 * 审批任务操作待同步暂存表 服务实现类
 * </p>
 *
 * @author liulx
 * @since 2021-03-26
 */
@Service
public class TaskOperateStageServiceImpl extends ServiceImpl<TaskOperateStageMapper, TaskOperateStage>
    implements TaskOperateStageService {

    @Reference(version = "0.0.1", check = false)
    ProcessTaskService processTaskService;

    @Autowired
    ContractCenterService contractCenterService;

    @Autowired
    private ApproveCenterService approveCenterService;

    @Autowired
    UserCenterService userCenterService;

    @Autowired
    SignCodeMapper signCodeMapper;

    @Autowired
    private TaskOperateStageService taskOperateStageService;

    @Autowired
    private MessageService messageService;

    /**
     * 根据任务 id 查询任务当前处理情况
     *
     * @param taskId 任务 id
     * @return 审批任务操作待同步暂存信息
     */
    @Override
    public TaskOperateStage getTaskOperateStageByTaskId(String taskId) {
        LambdaQueryWrapper<TaskOperateStage> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TaskOperateStage::getIsDeleted, "0");
        queryWrapper.eq(TaskOperateStage::getTaskId, taskId);
        return this.getOne(queryWrapper);
    }

    /**
     * 退回任务至发起人, 仅记录操作至临时记录表
     *
     * @param processInstanceId 流程实例 id
     * @param taskId 任务 id
     * @param userId 任务操作人
     * @param reason 退回意见
     */
    @Override
    public TaskOperateStage recallBack(String processInstanceId, String taskId, Integer userId, String reason,
        List<CommentFile> commentFiles) {
        TaskOperateStage operateStage = new TaskOperateStage();
        operateStage.setAssignee(userId);
        operateStage.setTaskId(taskId);
        operateStage.setProcessInstanceId(processInstanceId);
        operateStage.setIsCompleted(false);
        operateStage.setIsAllow(null);
        operateStage.setOperateType(String.valueOf(OperatingTypeEnum.RETURN.getCode()));
        operateStage.setComment(reason);
        operateStage.setIsDeleted(false);
        operateStage.setGmtCreate(LocalDateTime.now());
        operateStage.setCreateBy(userId);
        operateStage.setGmtModified(LocalDateTime.now());
        operateStage.setModifiedBy(userId);
        operateStage
            .setCommentFiles((commentFiles == null || commentFiles.isEmpty()) ? null : JSON.toJSONString(commentFiles));
        LambdaQueryWrapper<TaskOperateStage> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TaskOperateStage::getIsDeleted, "0");
        queryWrapper.eq(TaskOperateStage::getTaskId, taskId);
        this.saveOrUpdate(operateStage, queryWrapper);
        return operateStage;
    }

    /**
     * 驳回任务至发起人, 仅记录操作至临时记录表
     *
     * @param taskId 任务 id
     * @param userId 任务操作人
     * @param reason 退回意见
     * @return 临时记录落库对象
     */
    @Override
    public TaskOperateStage rejectToStartUser(String processInstanceId, String taskId, Integer userId, String reason,
        Integer type, List<CommentFile> commentFiles) {
        TaskOperateStage operateStage = new TaskOperateStage();
        operateStage.setTaskId(taskId);
        operateStage.setProcessInstanceId(processInstanceId);
        operateStage.setAssignee(userId);
        operateStage.setIsCompleted(false);
        operateStage.setIsAllow(null);
        // update by liwq on 2023-03-14 start
        // 操作类型，调整前：7驳回发起人；调整后：9驳回修改，10不同意；
        // rejectReasonFlag,0: 驳回修改， 1：不同意
        if (type == 0) {
            operateStage.setOperateType(String.valueOf(OperatingTypeEnum.REJECT_TO_STARTER_MODIFICATION.getCode()));
        } else {
            operateStage.setOperateType(String.valueOf(OperatingTypeEnum.REJECT_TO_STARTER_DISAGREE.getCode()));
        }
        // 审批处理意见附件列表
        if (commentFiles != null && commentFiles.size() > 0) {
            operateStage.setCommentFiles(JSONObject.toJSONString(commentFiles));
        }
        // update by liwq on 2023-03-14 end
        operateStage.setComment(reason);
        operateStage.setIsDeleted(false);
        operateStage.setCreateBy(userId);
        operateStage.setGmtCreate(LocalDateTime.now());
        operateStage.setModifiedBy(userId);
        operateStage.setGmtModified(LocalDateTime.now());
        LambdaQueryWrapper<TaskOperateStage> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TaskOperateStage::getIsDeleted, "0");
        queryWrapper.eq(TaskOperateStage::getTaskId, taskId);
        this.saveOrUpdate(operateStage, queryWrapper);
        return operateStage;
    }

    /**
     * 提交任务服务，即将流程流转下一节点 <br>
     * 注意：该提交函数仅支持同意这一种操作
     * 
     * @param completeTaskReq 提交任务请求信息
     * @param isToBeSigned 是否有待签章文件处理
     * @return 临时记录落库对象
     */
    @Override
    public TaskOperateStage complete(CompleteTaskReq completeTaskReq, Boolean isToBeSigned) {
        Integer userId = completeTaskReq.getUserId();
        TaskOperateStage operateStage = new TaskOperateStage();
        operateStage.setTaskId(completeTaskReq.getTaskId());
        operateStage.setProcessInstanceId(completeTaskReq.getProcessInstanceId());
        operateStage.setAssignee(userId);
        operateStage.setIsCompleted(false);
        operateStage.setIsAllow(null);
        operateStage.setOperateType(String.valueOf(OperatingTypeEnum.PASS.getCode()));
        operateStage.setLendersCaptcha(completeTaskReq.getLendersCaptcha());
        operateStage.setComment(completeTaskReq.getComment());
        operateStage
            .setCommentFiles((completeTaskReq.getCommentFiles() == null || completeTaskReq.getCommentFiles().isEmpty())
                ? null : JSON.toJSONString(completeTaskReq.getCommentFiles()));
        operateStage.setIsDeleted(false);
        operateStage.setCreateBy(userId);
        operateStage.setGmtCreate(LocalDateTime.now());
        operateStage.setModifiedBy(userId);
        operateStage.setGmtModified(LocalDateTime.now());
        operateStage.setIsToBeSigned(isToBeSigned);
        JSONArray operateDataJson = setBusinessOperateDataWithoutSign(completeTaskReq);
        operateStage.setBusinessOperateData(operateDataJson.isEmpty() ? null : operateDataJson.toJSONString());
        LambdaQueryWrapper<TaskOperateStage> queryWrapper = new LambdaQueryWrapper<>();
        queryWrapper.eq(TaskOperateStage::getIsDeleted, "0");
        queryWrapper.eq(TaskOperateStage::getTaskId, completeTaskReq.getTaskId());
        this.saveOrUpdate(operateStage, queryWrapper);
        return operateStage;
    }

    /**
     * 封装本次业务操作集合，并对待签章文件进行签章
     * 
     * <pre>
     *    示例：
     *[
     *     {
     *         "operate": 0,
     *         "data": [
     *             {
     *                 "name": "xxxx 附件1",
     *                 "value": "http://xxxxx.com/xxxx.pdf"
     *             },
     *             {
     *                 "name": "xxxx 附件2",
     *                 "value": "http://xxxxx.com/xxxx.pdf"
     *             }
     *         ]
     *     },
     *     {
     *         "operate": 1,
     *         "data": [
     *             {
     *                 "name": "xxxx 签章文件1",
     *                 "value": "http://xxxxx.com/xxxx.pdf"
     *             },
     *             {
     *                 "name": "xxxx 签章文件2",
     *                 "value": "http://xxxxx.com/xxxx.pdf"
     *             }
     *         ]
     *     },
     *     {
     *         "operate": 2,
     *         "data": [
     *             {
     *                 "name": "*小明",
     *                 "value": "1"
     *             }
     *         ]
     *     },
     *     {
     *         "operate": 3,
     *         "data": [
     *             {
     *                 "name": "放款方式确认",
     *                 "value": "0"
     *             }
     *         ]
     *     }
     * ]
     * </pre>
     * 
     * @param completeTaskReq
     * @return 业务操作集合
     */
    protected JSONArray setBusinessOperateDataWithoutSign(CompleteTaskReq completeTaskReq) {
        // 业务处理数据最终集合结果
        JSONArray array = new JSONArray();
        // Step 1. 设置下一节点审批人
        // 选择下一节点审批人
        String selectedApproverStr = completeTaskReq.getSelectedApprover();
        if (StringUtils.isNotEmpty(selectedApproverStr)) {
            String[] split = selectedApproverStr.split("u-");
            Integer selectedApprover = Integer.valueOf(split[split.length - 1]);
            UserInfoDto userInfo =
                userCenterService.getUserInfo(selectedApprover, SystemConstant.CHANNEL, selectedApprover, null);
            if (userInfo == null || userInfo.getUserBaseInformationDto() == null) {
                throw new BusinessException("无该用户信息", "ECMA0001");
            }
            JSONArray nextUserArray = new JSONArray();
            JSONObject nextUserObject = new JSONObject();
            nextUserObject.put("name", userInfo.getUserBaseInformationDto().getUserName());
            nextUserObject.put("value", selectedApprover);
            nextUserArray.add(nextUserObject);

            JSONObject nextUserData = new JSONObject();
            nextUserData.put("operate", 2);
            nextUserData.put("data", nextUserArray);
            array.add(nextUserData);
        }

        // Step 2. 设置上传的文件
        // 上传文件列表
        JSONArray fileArray = new JSONArray();
        List<UploadFileBean> uploadFiles = completeTaskReq.getUploadFiles();
        if (uploadFiles != null && !uploadFiles.isEmpty()) {
            uploadFiles.forEach(file -> {
                JSONObject object = new JSONObject();
                object.put("name", file.getFileName());
                object.put("value", file.getFileUrl());
                fileArray.add(object);
            });
            JSONObject fileData = new JSONObject();
            fileData.put("operate", 0);
            fileData.put("data", fileArray);
            array.add(fileData);
        }

        // Step 3. 封装选择的放款方式
        // completeTaskReq.getLendersCaptcha()
        String loanDisbursementMethod = completeTaskReq.getLoanDisbursementMethod();
        if (StringUtils.isNotEmpty(loanDisbursementMethod)) {
            JSONArray arrayTemp = new JSONArray();
            JSONObject object = new JSONObject();
            object.put("name", "放款方式");
            object.put("value", loanDisbursementMethod);
            arrayTemp.add(object);
            JSONObject fileData = new JSONObject();
            fileData.put("operate", 3);
            fileData.put("data", arrayTemp);
            array.add(fileData);
        }

        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "封装业务操作集合: {}", array);
        return array;
    }

    /**
     * 对待签章文件签章并更新回操作记录，同时执行后续流程运转等服务
     * 
     * @param completeTaskReq 同意操作、提交任务请求信息（签章时上送的请求信息）
     * @param taskOperateStage 任务操作待同步暂存信息
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void signAndAppendToBusinessOperate(CompleteTaskReq completeTaskReq, TaskOperateStage taskOperateStage) {
        // 对待签章文件进行签章
        // 签章文件列表
        JSONArray signFileArray = new JSONArray();
        // 签章文件
        List<BusinessFormDataForFile> signFiles = completeTaskReq.getSignFiles();
        if (signFiles != null && !signFiles.isEmpty()) {
            Optional.ofNullable(completeTaskReq.getCode())
                .orElseThrow(() -> new BusinessException("验证码不能为空", "ECMA0001"));
            LambdaQueryWrapper<SignCode> codeQueryWrapper = new LambdaQueryWrapper<>();
            codeQueryWrapper.eq(SignCode::getTaskId, completeTaskReq.getTaskId());
            codeQueryWrapper.eq(SignCode::getUserId, completeTaskReq.getUserId());
            // Integer signCode = signCodeMapper.selectCount(codeQueryWrapper);
            SignCode signCode = signCodeMapper.selectOne(codeQueryWrapper);
            if (signCode == null) {
                throw new BusinessException("请先获取验证码", "ECMA0001");
            }
            signCode.setCaptcha(completeTaskReq.getCode());
            // 更新验证码至数据库中
            signCodeMapper.updateById(signCode);

            // 待签章文件列表（合约中心服务使用）
            List<SigningParamBean> signingParamBeanList = new ArrayList<>();
            // 临时存储文件， key 为 MarkBit，用于关联调用 e签宝签章服务后，签章文件后和签章文件前的关系
            Map<String, JSONObject> tempFiles = new HashMap<>(16);
            String accountId = completeTaskReq.getAccountId();
            Integer sealId = completeTaskReq.getSealId();

            signFiles.forEach(signFile -> {
                String secureRandomId = IDUtil.getSecureRandomId();
                JSONObject object = new JSONObject();
                object.put("name", signFile.getDisplayName());
                object.put("sourceValue", signFile.getValue());
                object.put("id", signFile.getId());
                tempFiles.put(secureRandomId, object);
                // 转换为签章服务使用的 bean
                SigningParamBean signingParamBean = signBeanTransfer(signFile);
                signingParamBean.setMarkBit(secureRandomId);
                signingParamBeanList.add(signingParamBean);
            });
            // 调用签章服务对文件进行签章
            ServiceResult<MultiSignedResultDTO> serviceResult =
                contractCenterService.signPdf(completeTaskReq.getUserId(), accountId, completeTaskReq.getMobile(),
                    completeTaskReq.getCode(), sealId, signingParamBeanList);
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批调用电子签章-指定手机号签章服务,resp: {}",
                serviceResult);
            MultiSignedResultDTO signedResultDTO = serviceResult.getResult();
            List<SignResultBean> failList = signedResultDTO.getFailList();
            if (failList != null && !failList.isEmpty()) {
                throw new BusinessException(failList.get(0).getMsg(), "ECMA0001");
            }
            List<SignedResultDTO> successList = signedResultDTO.getSuccessList();
            successList.forEach(signedResult -> {
                JSONObject jsonObject = tempFiles.get(signedResult.getMarkBit());
                jsonObject.put("value", signedResult.getFileUrl());
                signFileArray.add(jsonObject);
            });
            JSONObject signFileData = new JSONObject();
            signFileData.put("operate", 1);
            signFileData.put("data", signFileArray);

            String businessOperateData = taskOperateStage.getBusinessOperateData();
            JSONArray businessOperateDataArray = new JSONArray();
            if (StringUtils.isNotEmpty(businessOperateData)) {
                businessOperateDataArray = JSON.parseArray(businessOperateData);
            }
            businessOperateDataArray.add(signFileData);
            // 将签章后的文件也更新回操作记录中，以便后续推送消息
            taskOperateStage.setBusinessOperateData(JSON.toJSONString(businessOperateDataArray));
            // taskOperateStage.setIsToBeSigned(false);
            // 具体更新在推送消息前, 详见 com/ynet/middleground/mobile/approve/service/MessageService.java:422
            // this.updateById(taskOperateStage);
            IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "任务: {} 签章后完整业务操作集合: {}",
                completeTaskReq.getTaskId(), businessOperateDataArray);
            updateToBeSignedAndInvoke(taskOperateStage, tempFiles);
        }
    }

    /**
     * 签章后文件更新回流程中，将流程流转至下一节点，并推送相关消息
     * 
     * @param taskOperateStage 当前任务操作记录信息
     * @param tempSignedFiles 签章后的文件集合
     */
    public void updateToBeSignedAndInvoke(TaskOperateStage taskOperateStage, Map<String, JSONObject> tempSignedFiles) {
        Map<String, JSONObject> signedFilesCache = tempSignedFiles.entrySet().stream()
            .collect(Collectors.toMap(e -> e.getValue().getString("id"), Map.Entry::getValue));

        QueryTaskDetailReq queryTaskDetailReq = new QueryTaskDetailReq();
        queryTaskDetailReq.setTaskId(taskOperateStage.getTaskId());
        queryTaskDetailReq.setChannel(SystemConstant.CHANNEL);
        queryTaskDetailReq.setTraceId(IDUtil.getSecureRandomId());
        queryTaskDetailReq.setRequestTime(new Date());
        queryTaskDetailReq.setOperationUserId(taskOperateStage.getCreateBy());
        IfpLogger.info(Thread.currentThread().getStackTrace()[1].getMethodName(), "移动审批调用查询任务服务, reqParams: {}",
            queryTaskDetailReq.toString());
        ServiceResult<TaskDTO> serviceResultTask = processTaskService.getTask(queryTaskDetailReq);
        if (!serviceResultTask.isSuccess()) {
            throw new BusinessException(serviceResultTask.getErrorCode(), serviceResultTask.getErrorMsg());
        }
        TaskDTO taskDTO = serviceResultTask.getResult();
        Map<String, Object> processVariables = taskDTO.getProcessVariables();
        // 获取所有待签章文件
        Object o = processVariables.get(WorkflowConstants.FLOW_LIST_OF_FILES_TO_BE_SIGNED);
        Map<String, Object> processVariablesUpdate = new HashMap<>(2);
        if (o instanceof List) {
            List<FilesToBeSignedBean> filesToBeSigned = (List<FilesToBeSignedBean>)o;
            filesToBeSigned.stream().filter(bean -> signedFilesCache.containsKey(bean.getId()))
                .forEach(bean -> bean.setValue(signedFilesCache.get(bean.getId()).getString("value")));
            // 将签章之后的文件 URL 地址更新回待签章列表中
            processVariablesUpdate.put(WorkflowConstants.FLOW_LIST_OF_FILES_TO_BE_SIGNED, filesToBeSigned);
        }

        // 处理完将待签章标识更改为 无待签章文件
        taskOperateStage.setIsToBeSigned(false);
        taskOperateStage.setGmtModified(LocalDateTime.now());
        taskOperateStageService.updateById(taskOperateStage);

        String nextAssignee =
            taskOperateStageService.getNextAssigneeByOperateData(taskOperateStage.getBusinessOperateData());
        // 审批中心执行完成后会调用移动审批触发 钉钉通知，注意，有可能导致上述记录更新事物未提交，导致通知时查询的数据是未更新的数据，从而无法发送通知，故从外部重新发送
        ServiceResult<Object> serviceResult =
            approveCenterService.invokeApproveCompleteTask(taskOperateStage, nextAssignee, processVariablesUpdate);

        // 发送执行后通知
        String errMsg =
            approveCenterService.pushApproveAfterMsgQueue(serviceResult, taskOperateStage, OperatingTypeEnum.PASS);
        if (StringUtils.isNotEmpty(errMsg)) {
            throw new BusinessException(errMsg, "ECMA0100");
        }
        // 等事物提交后再发送钉钉通知，此场景下仅为 MsgTypeEnum.TODO 待办类型的通知
        TransactionUtils.afterCommitSyncExecute(() -> messageService
            .sendDingtalkNotificationAfterApproval(taskOperateStage, MsgTypeEnum.TODO, OperatingTypeEnum.PASS));
    }

    /**
     * 转换签章文件 Bean
     * 
     * @param signFile
     * @return
     */
    protected SigningParamBean signBeanTransfer(BusinessFormDataForFile signFile) {
        SigningParamBean signingParamBean = new SigningParamBean();
        signingParamBean.setPdfUrl(signFile.getValue());
        BusinessSignInfoData signInfo = signFile.getSignInfo();
        signingParamBean.setSignType(signInfo.getSignType());
        SignPos signPos = new SignPos();
        signPos.setPosType(signInfo.getPosType());
        signPos.setPosPage(signInfo.getPosPage());
        signPos.setPosX(signInfo.getPosX());
        signPos.setPosY(signInfo.getPosY());
        signPos.setKey(signInfo.getKey());
        signPos.setWidth(signInfo.getWidth());
        signPos.setCacellingSign(signInfo.isCacellingSign());
        signPos.setAddSignTime(signInfo.isAddSignTime());
        signingParamBean.setSignPos(signPos);
        return signingParamBean;
    }

    /**
     * 根据操作记录 json 数据获取下一节点审批人 <br>
     * 示例参考 {@link TaskOperateStageServiceImpl#setBusinessOperateDataWithoutSign(CompleteTaskReq)}
     *
     * @param operateDataJSONString 操作记录 json 数据
     * @return 下一节点审批人
     */
    @Override
    public String getNextAssigneeByOperateData(String operateDataJSONString) {
        if (operateDataJSONString == null) {
            return null;
        }

        JSONArray jsonArray = JSONArray.parseArray(operateDataJSONString);
        for (Object o : jsonArray) {
            JSONObject obj = (JSONObject)o;
            Integer operate = obj.getInteger("operate");
            if (operate == 2) {
                // 操作记录中，选择下一届点审批人，只能是一个人，所以仅取第一个元素即可
                JSONArray data = obj.getJSONArray("data");
                JSONObject jsonObject = data.getJSONObject(0);
                return jsonObject.getString("value");
            }
        }
        return null;
    }

}
